/* CsJni - .NET / Java Bridge
 * 
 * Copyright 2010 Robert E. Burke Licensed under the Apache License, Version 
 * 2.0 (the "License"); you may not use this file except in compliance with 
 * the License. You may obtain a copy of the License at 
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable 
 * law or agreed to in writing, software distributed under the License is 
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
 * KIND, either express or implied. See the License for the specific language 
 * governing permissions and limitations under the License. 
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Globalization;
using Microsoft.Win32;

namespace CsJni.PlatformAbstraction
{
    internal class WindowsPlatform : Platform
    {
        public WindowsPlatform(JavaVMConfiguration cfg) 
            : base(cfg)
        {
        }

        private VfprintfExtractor mVfprintfExtractor = null;

        public override string PlatformName
        {
            get { return "Windows"; }
        }

        public override void FaultVfPrintf()
        {
            mVfprintfExtractor = new VfprintfExtractor();
            mVfprintfExtractor.Init(this, Cfg.CrtLib);
        }

        public override string ExtractVfprintf(IntPtr filePointer, IntPtr format, IntPtr arguments)
        {
            return mVfprintfExtractor.ExtractVfprintf(filePointer, format, arguments);
        }

        public override IntPtr LoadDynamicLibrary(string fileName)
        {
            IntPtr libraryHandle = NativeMethods.LoadLibrary(fileName);
            if (libraryHandle == IntPtr.Zero)
            {
                throw new Win32Exception(Marshal.GetLastWin32Error(), "LoadLibrary failed: " + fileName);
            }
            return libraryHandle;
        }

        public override void FreeDynamicLibrary(IntPtr libraryHandle)
        {
            int res = NativeMethods.FreeLibrary(libraryHandle);
            if (res == 0)
            {
                throw new Win32Exception(Marshal.GetLastWin32Error(), "FreeLibrary failed.");
            }
        }

        public override T GetDelegateForLibraryMethod<T>(IntPtr libraryHandle, string methodName)
        {
            IntPtr proc = NativeMethods.GetProcAddress(libraryHandle, methodName);
            if (proc == IntPtr.Zero)
            {
                throw new Win32Exception(Marshal.GetLastWin32Error(), "GetProcAddress failed: " + methodName);
            }
            return (T)(object)Marshal.GetDelegateForFunctionPointer(proc, typeof(T));
        }

        public override string FindJvmLibraryWithPath(string path)
        {
            return ExpandAndVerifyPath(path, ExpandPathType.Executable, false);
        }

        // See http://svn.codehaus.org/groovy/trunk/groovy/modules/native_launcher/source/jst_winreg.c
        // Could also look at these registry keys:
        // SOFTWARE\\JRockit\\Java Development Kit
        // SOFTWARE\\JavaSoft\\Java Runtime Environment
        // SOFTWARE\\JRockit\\Java Runtime Environment
        public override string LookupJvmLibrary(string version)
        {
            if (version.Length == 0)
            {
                string keyPath = @"SOFTWARE\JavaSoft\Java Runtime Environment";
                using (RegistryKey key = Registry.LocalMachine.OpenSubKey(keyPath, false))
                {
                    version = (string)key.GetValue("CurrentVersion", "");
                }
            }
            if (version.Length == 0)
            {
                throw new JniException(JniResourceManager.Instance.GetString("jvmPathFindInvalidRegistry"));
            }
            string versionKeyPath = @"SOFTWARE\JavaSoft\Java Runtime Environment\" + version;
            using (RegistryKey key = Registry.LocalMachine.OpenSubKey(versionKeyPath, false))
            {
                return ExpandAndVerifyPath((string)key.GetValue("RuntimeLib", ""), ExpandPathType.Executable, false);
            }
        }

        public override string ExpandPath(string inputPath, ExpandPathType expandPathType)
        {
            if (expandPathType == ExpandPathType.Executable)
            {
                string testSearch = SearchPath(NormalizePathDirectorySeparators(inputPath));
                if (testSearch != null)
                {
                    return testSearch;
                }
            }
            return base.ExpandPath(inputPath, expandPathType);
        }

        public override void Release()
        {
            if (mVfprintfExtractor != null)
            {
                mVfprintfExtractor.Free();
                mVfprintfExtractor = null;
            }
        }

        private static string SearchPath(string input)
        {
            const int _MAX_PATH = 260;
            StringBuilder buffer = new StringBuilder(_MAX_PATH);
            int result = NativeMethods.SearchPath(null, input, null, _MAX_PATH, buffer, IntPtr.Zero);
            if (result == 0)
            {
                int lastError = Marshal.GetLastWin32Error();
                if (lastError == 2) // ERROR_FILE_NOT_FOUND
                {
                    return null;
                }
                throw new Win32Exception(lastError);
            }
            return buffer.ToString();
        }

        private sealed class VfprintfExtractor : AbstractVfprintfExtractor
        {
            [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
            private delegate int vsprintf_Handler(
                [MarshalAs(UnmanagedType.LPStr)] StringBuilder dest, IntPtr format, IntPtr argList);
            [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
            private delegate int vscprintf_Handler(
                IntPtr format, IntPtr argList);

            private vsprintf_Handler vsprintf;
            private vscprintf_Handler vscprintf;

            public VfprintfExtractor()
            {
            }
            protected override void InitMethods()
            {
                vsprintf = Platform.GetDelegateForLibraryMethod<vsprintf_Handler>(LibraryHandle, "vsprintf");
                vscprintf = Platform.GetDelegateForLibraryMethod<vscprintf_Handler>(LibraryHandle, "_vscprintf");
            }
            protected override void FreeMethods()
            {
                vsprintf = null;
                vscprintf = null;
            }

            public override string ExtractVfprintf(IntPtr filePointer, IntPtr format, IntPtr arguments)
            {
                if (LibraryHandle == IntPtr.Zero || vsprintf == null || vscprintf == null)
                {
                    throw new JniException("ExtractVfprintf cannot be called now.");
                }
                int bufferLen = vscprintf(format, arguments) + 1;
                StringBuilder buffer = new StringBuilder(bufferLen);
                int success = vsprintf(buffer, format, arguments);
                if (success > 0)
                {
                    return buffer.ToString();
                }
                else
                {
                    string message = JniResourceManager.Instance.GetString("jniVsprintfError") +
                        success.ToString(CultureInfo.InvariantCulture);
                    throw new JniException(message);
                }
            }
        }

        private sealed class NativeMethods
        {
            private NativeMethods()
            {
            }

            [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            public static extern IntPtr LoadLibrary(string fileName);

            [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            public static extern int FreeLibrary(IntPtr hModule);

            [DllImport("kernel32.dll", SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
            public static extern IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string procName);

            [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            public static extern int SearchPath(string path, string fileName, string extension, int bufferLen,
                StringBuilder buffer, IntPtr filePart);
        }
    }
}
